package com.google.code.irouter.batch.api;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import com.google.code.irouter.model.Guide;
import com.google.code.irouter.utils.LocalUtil;
import com.google.code.irouter.utils.LogUtil;
import com.google.code.irouter.utils.PropertyUtil;

public abstract class Detector {
	
	public enum LOAD_TYPE {STREAM, TEXT};
	
	private int reconnectTimes = 0;
	
	private boolean success;
	
	private boolean reconnect;

	protected abstract List<DetectorResult> resolve(Guide guide, InputStream inputStream, List<DetectorResult> result) throws Exception;
	protected abstract List<DetectorResult> resolve(Guide guide, String text, List<DetectorResult> result) throws Exception;
	
	protected abstract boolean isReconnect(Exception exception, boolean result);
	
	protected abstract LOAD_TYPE getLoadType();

	protected boolean isSuccess() {
		return success;
	}

	protected void setSuccess(boolean success) {
		this.success = success;
	}

	protected boolean isReconnect() {
		return reconnect;
	}

	protected void setReconnect(boolean reconnect) {
		this.reconnect = reconnect;
	}
	
	protected int getRetryTimes() {
		int result = 3;
		String retryTimes = PropertyUtil.getProperty("i-router.retry.times");
		if (retryTimes != null) {
			result = Integer.valueOf(retryTimes);
		}
		return result;
	}
	
	private InputStream connectToStream(Guide guide) throws Exception {
		return ((HttpURLConnection) (new URL(guide.getResource().getValue())).openConnection()).getInputStream();
	}
	
	private String connectToString(Guide guide) throws Exception {
		URL url = new URL(guide.getResource().getValue());
        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), getDefaultCharset()));
        String line;
        StringBuffer stringBuffer = new StringBuffer();
        while ((line = reader.readLine()) != null) {
        	stringBuffer.append(line);
        }
        reader.close();
		return stringBuffer.toString();
	}
	
	protected String getDefaultCharset(){
		return PropertyUtil.getProperty("i-router.default.charset");
	}
	
	private List<DetectorResult> process(Guide guide) {
		List<DetectorResult> result = new ArrayList<DetectorResult>();
		setSuccess(true);
		setReconnect(false);
		try {
			InputStream inputStream = null;
			String text = null;
			if(getLoadType().equals(LOAD_TYPE.STREAM)) {
				inputStream = connectToStream(guide);
			} else {
				text = connectToString(guide);
			}
			LogUtil.info(
					LogUtil.TYPE.BATCH,
					Detector.class,
					null,
					LocalUtil.getResource("com.google.code.irouter.batch.api.detector.connect.success")
			);
			try {
				if(getLoadType().equals(LOAD_TYPE.STREAM)) {
					result = resolve(guide, inputStream, result);
				} else {
					result = resolve(guide, text, result);
				}
				LogUtil.info(
						LogUtil.TYPE.BATCH,
						Detector.class,
						null,
						LocalUtil.getResource("com.google.code.irouter.batch.api.detector.resolve.success")
				);
			} catch (Exception exception) {
				setSuccess(false);
				setReconnect(isReconnect(exception, false));
				LogUtil.warn(
						LogUtil.TYPE.BATCH, 
						Detector.class, 
						null,
						LocalUtil.getResource("com.google.code.irouter.batch.api.detector.resolve.fail"),
						exception
				);
			}
		} catch (Exception exception) {
			if (SocketTimeoutException.class.isInstance(exception)) {
				setSuccess(false);
				setReconnect(true);
			} else {
				setSuccess(false);
				setReconnect(false);
			}
			LogUtil.warn(
					LogUtil.TYPE.BATCH, 
					Detector.class, 
					null,
					LocalUtil.getResource("com.google.code.irouter.batch.api.detector.connect.fail"),
					exception
			);
		}
		
		return result;
	}

	public List<DetectorResult> execute(Guide guide) {
		List<DetectorResult> result = process(guide);
		if(!isSuccess())
			if(this.reconnect)
				if (this.reconnectTimes++ < getRetryTimes())
					result = execute(guide);
		return result;
	}

}
